{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "RandBits MNIST.ipynb",
      "provenance": [],
      "collapsed_sections": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "d8806065a5054b8da9767e4bb24af477": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HBoxModel",
          "state": {
            "_view_name": "HBoxView",
            "_dom_classes": [],
            "_model_name": "HBoxModel",
            "_view_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_view_count": null,
            "_view_module_version": "1.5.0",
            "box_style": "",
            "layout": "IPY_MODEL_1747f5b05a9c480c8311e56b9be11d2a",
            "_model_module": "@jupyter-widgets/controls",
            "children": [
              "IPY_MODEL_8ecae8435e1244e2bac2b3a75971cdef",
              "IPY_MODEL_bce306cd6a544f2a8b7fdbcfe523d802"
            ]
          }
        },
        "1747f5b05a9c480c8311e56b9be11d2a": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "state": {
            "_view_name": "LayoutView",
            "grid_template_rows": null,
            "right": null,
            "justify_content": null,
            "_view_module": "@jupyter-widgets/base",
            "overflow": null,
            "_model_module_version": "1.2.0",
            "_view_count": null,
            "flex_flow": null,
            "width": null,
            "min_width": null,
            "border": null,
            "align_items": null,
            "bottom": null,
            "_model_module": "@jupyter-widgets/base",
            "top": null,
            "grid_column": null,
            "overflow_y": null,
            "overflow_x": null,
            "grid_auto_flow": null,
            "grid_area": null,
            "grid_template_columns": null,
            "flex": null,
            "_model_name": "LayoutModel",
            "justify_items": null,
            "grid_row": null,
            "max_height": null,
            "align_content": null,
            "visibility": null,
            "align_self": null,
            "height": null,
            "min_height": null,
            "padding": null,
            "grid_auto_rows": null,
            "grid_gap": null,
            "max_width": null,
            "order": null,
            "_view_module_version": "1.2.0",
            "grid_template_areas": null,
            "object_position": null,
            "object_fit": null,
            "grid_auto_columns": null,
            "margin": null,
            "display": null,
            "left": null
          }
        },
        "8ecae8435e1244e2bac2b3a75971cdef": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "FloatProgressModel",
          "state": {
            "_view_name": "ProgressView",
            "style": "IPY_MODEL_b8c000a9d5974ea58a96d724eaf43beb",
            "_dom_classes": [],
            "description": "Dl Completed...: 100%",
            "_model_name": "FloatProgressModel",
            "bar_style": "success",
            "max": 4,
            "_view_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "value": 4,
            "_view_count": null,
            "_view_module_version": "1.5.0",
            "orientation": "horizontal",
            "min": 0,
            "description_tooltip": null,
            "_model_module": "@jupyter-widgets/controls",
            "layout": "IPY_MODEL_8f57b328127541f0b1f48bd4ad1a016c"
          }
        },
        "bce306cd6a544f2a8b7fdbcfe523d802": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "state": {
            "_view_name": "HTMLView",
            "style": "IPY_MODEL_741b86714bdc4b1c8ee4eafcbf8d4115",
            "_dom_classes": [],
            "description": "",
            "_model_name": "HTMLModel",
            "placeholder": "​",
            "_view_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "value": " 4/4 [00:02&lt;00:00,  1.99 file/s]",
            "_view_count": null,
            "_view_module_version": "1.5.0",
            "description_tooltip": null,
            "_model_module": "@jupyter-widgets/controls",
            "layout": "IPY_MODEL_d0d83413d905432588674bed26b36979"
          }
        },
        "b8c000a9d5974ea58a96d724eaf43beb": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "ProgressStyleModel",
          "state": {
            "_view_name": "StyleView",
            "_model_name": "ProgressStyleModel",
            "description_width": "initial",
            "_view_module": "@jupyter-widgets/base",
            "_model_module_version": "1.5.0",
            "_view_count": null,
            "_view_module_version": "1.2.0",
            "bar_color": null,
            "_model_module": "@jupyter-widgets/controls"
          }
        },
        "8f57b328127541f0b1f48bd4ad1a016c": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "state": {
            "_view_name": "LayoutView",
            "grid_template_rows": null,
            "right": null,
            "justify_content": null,
            "_view_module": "@jupyter-widgets/base",
            "overflow": null,
            "_model_module_version": "1.2.0",
            "_view_count": null,
            "flex_flow": null,
            "width": null,
            "min_width": null,
            "border": null,
            "align_items": null,
            "bottom": null,
            "_model_module": "@jupyter-widgets/base",
            "top": null,
            "grid_column": null,
            "overflow_y": null,
            "overflow_x": null,
            "grid_auto_flow": null,
            "grid_area": null,
            "grid_template_columns": null,
            "flex": null,
            "_model_name": "LayoutModel",
            "justify_items": null,
            "grid_row": null,
            "max_height": null,
            "align_content": null,
            "visibility": null,
            "align_self": null,
            "height": null,
            "min_height": null,
            "padding": null,
            "grid_auto_rows": null,
            "grid_gap": null,
            "max_width": null,
            "order": null,
            "_view_module_version": "1.2.0",
            "grid_template_areas": null,
            "object_position": null,
            "object_fit": null,
            "grid_auto_columns": null,
            "margin": null,
            "display": null,
            "left": null
          }
        },
        "741b86714bdc4b1c8ee4eafcbf8d4115": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "state": {
            "_view_name": "StyleView",
            "_model_name": "DescriptionStyleModel",
            "description_width": "",
            "_view_module": "@jupyter-widgets/base",
            "_model_module_version": "1.5.0",
            "_view_count": null,
            "_view_module_version": "1.2.0",
            "_model_module": "@jupyter-widgets/controls"
          }
        },
        "d0d83413d905432588674bed26b36979": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "state": {
            "_view_name": "LayoutView",
            "grid_template_rows": null,
            "right": null,
            "justify_content": null,
            "_view_module": "@jupyter-widgets/base",
            "overflow": null,
            "_model_module_version": "1.2.0",
            "_view_count": null,
            "flex_flow": null,
            "width": null,
            "min_width": null,
            "border": null,
            "align_items": null,
            "bottom": null,
            "_model_module": "@jupyter-widgets/base",
            "top": null,
            "grid_column": null,
            "overflow_y": null,
            "overflow_x": null,
            "grid_auto_flow": null,
            "grid_area": null,
            "grid_template_columns": null,
            "flex": null,
            "_model_name": "LayoutModel",
            "justify_items": null,
            "grid_row": null,
            "max_height": null,
            "align_content": null,
            "visibility": null,
            "align_self": null,
            "height": null,
            "min_height": null,
            "padding": null,
            "grid_auto_rows": null,
            "grid_gap": null,
            "max_width": null,
            "order": null,
            "_view_module_version": "1.2.0",
            "grid_template_areas": null,
            "object_position": null,
            "object_fit": null,
            "grid_auto_columns": null,
            "margin": null,
            "display": null,
            "left": null
          }
        }
      }
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pEAjCLI8QCjU"
      },
      "source": [
        "##### Copyright 2020 Google LLC.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "you may not use this file except in compliance with the License.\n",
        "You may obtain a copy of the License at\n",
        "\n",
        "https://www.apache.org/licenses/LICENSE-2.0\n",
        "\n",
        "Unless required by applicable law or agreed to in writing, software\n",
        "distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "See the License for the specific language governing permissions and\n",
        "limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2lCKaZSM2Ac0"
      },
      "source": [
        "## RandBits MNIST using Contrastive Learning."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PZrwIvTl05P6"
      },
      "source": [
        "This notebook trains an unsupervised model on the RandBits MNIST dataset using contrastive learning, similar to the results shown in Figure 5(a) in ***Intriguing Properties of Contrastive Losses***."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "0amaY7x4wgGr",
        "cellView": "form"
      },
      "source": [
        "#@title Imports.\n",
        "import tensorflow.compat.v2 as tf\n",
        "tf.enable_v2_behavior()\n",
        "\n",
        "import tensorflow_datasets as tfds\n",
        "import matplotlib.pyplot as plt\n",
        "import seaborn as sns\n",
        "import math\n",
        "import pandas as pd"
      ],
      "execution_count": 9,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "dAa3IRTov5Bk",
        "cellView": "form"
      },
      "source": [
        "#@title Data preprocessing.\n",
        "def random_crop_and_resize(image):\n",
        "  sample_distorted_bounding_box = tf.image.sample_distorted_bounding_box(\n",
        "      image_size=tf.shape(image),\n",
        "      bounding_boxes=tf.constant(\n",
        "          [0.0, 0.0, 1.0, 1.0], dtype=tf.float32, shape=[1, 1, 4]),\n",
        "      min_object_covered=0.1,\n",
        "      aspect_ratio_range=(3. / 4, 4. / 3.),\n",
        "      area_range=(0.5, 1.0),\n",
        "      max_attempts=100,\n",
        "      use_image_if_no_bounding_boxes=True)\n",
        "  bbox_begin, bbox_size, _ = sample_distorted_bounding_box\n",
        "\n",
        "  # Crop the image to the specified bounding box.\n",
        "  offset_y, offset_x, _ = tf.unstack(bbox_begin)\n",
        "  target_height, target_width, _ = tf.unstack(bbox_size)\n",
        "  image = tf.image.crop_to_bounding_box(\n",
        "      image, offset_y, offset_x, target_height, target_width)\n",
        "\n",
        "  return tf.image.resize(image, [28, 28], method=tf.image.ResizeMethod.BILINEAR)\n",
        "\n",
        "def hash_image_to_bits(image, extra_channel_bits):\n",
        "  hash = tf.compat.v1.strings.to_hash_bucket_fast(\n",
        "      tf.image.encode_jpeg(image),\n",
        "      num_buckets=2**extra_channel_bits)\n",
        "  bits = tf.cast(\n",
        "      tf.math.mod(\n",
        "            tf.bitwise.right_shift(tf.expand_dims([hash], 1),\n",
        "            tf.range(extra_channel_bits, dtype=tf.int64)), 2),\n",
        "      tf.float32)\n",
        "  return bits\n",
        "\n",
        "def pack_extra_channels(image, bits):\n",
        "  extra_channel_bits = tf.shape(bits)[-1]\n",
        "  bits = tf.broadcast_to(bits, [28, 28, extra_channel_bits])\n",
        "  return tf.concat([image, tf.cast(bits, tf.float32)], axis=-1)\n",
        "\n",
        "def get_process_fns(extra_channel_bits):\n",
        "  def preprocess_train_fn(image, label):\n",
        "    bits = hash_image_to_bits(image, extra_channel_bits)\n",
        "    image = tf.image.convert_image_dtype(image, dtype=tf.float32)\n",
        "    label = tf.cast(label, tf.int32)\n",
        "\n",
        "    image_a = random_crop_and_resize(image)\n",
        "    image_b = random_crop_and_resize(image)\n",
        "\n",
        "    # Pack extra channels.\n",
        "    if extra_channel_bits > 0:\n",
        "      image_a = pack_extra_channels(image_a, bits)\n",
        "      image_b = pack_extra_channels(image_b, bits)\n",
        "\n",
        "    image = tf.stack([image_a, image_b], axis=0)  # [2, h, w, c]\n",
        "    return (image, label)\n",
        "\n",
        "  def preprocess_eval_fn(image, label):\n",
        "    bits = hash_image_to_bits(image, extra_channel_bits)\n",
        "    image = tf.image.convert_image_dtype(image, dtype=tf.float32)\n",
        "    label = tf.cast(label, tf.int32)\n",
        "    if extra_channel_bits > 0:\n",
        "      image = pack_extra_channels(image, bits)\n",
        "    return (image, label)\n",
        "\n",
        "  return preprocess_train_fn, preprocess_eval_fn"
      ],
      "execution_count": 3,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "IjaSKhqb1qFV",
        "cellView": "form"
      },
      "source": [
        "#@title Objective functions.\n",
        "cls_loss_object = tf.keras.losses.CategoricalCrossentropy(\n",
        "    from_logits=True,\n",
        "    reduction=tf.keras.losses.Reduction.NONE)\n",
        "\n",
        "def get_cls_loss(labels, outputs):\n",
        "  return tf.reduce_mean(cls_loss_object(labels, outputs))\n",
        "\n",
        "def get_contrastive_loss(z1, z2, nt_xent_temp):   # [batch_size, dim]\n",
        "  batch_size = tf.shape(z1)[0]\n",
        "  dim = tf.shape(z1)[1]\n",
        "\n",
        "  z1 = tf.math.l2_normalize(z1, -1)\n",
        "  z2 = tf.math.l2_normalize(z2, -1)\n",
        "\n",
        "  sim = tf.matmul(z1, z2, transpose_b=True)  # [batch_size, batch_size]\n",
        "  sim /= nt_xent_temp\n",
        "\n",
        "  labels = tf.eye(batch_size)\n",
        "\n",
        "  loss = (\n",
        "      get_cls_loss(labels, sim) +\n",
        "      get_cls_loss(labels, tf.transpose(sim))\n",
        "  )\n",
        "  return tf.reduce_mean(loss), sim"
      ],
      "execution_count": 4,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "0usbo7eUseR8",
        "cellView": "form"
      },
      "source": [
        "#@title Model.\n",
        "def dense_bn_relu(units):\n",
        "  return tf.keras.Sequential([\n",
        "      tf.keras.layers.Dense(\n",
        "          units, use_bias=False,\n",
        "          kernel_regularizer=tf.keras.regularizers.l2(1e-4)),\n",
        "      tf.keras.layers.BatchNormalization(center=True, scale=True),\n",
        "      tf.keras.layers.ReLU()\n",
        "  ])\n",
        "\n",
        "def conv2d_bn_relu(filters, kernel_size, strides):\n",
        "  return tf.keras.Sequential([\n",
        "      tf.keras.layers.Conv2D(\n",
        "          filters, kernel_size, strides, use_bias=False,\n",
        "          kernel_regularizer=tf.keras.regularizers.l2(1e-4)),\n",
        "      tf.keras.layers.BatchNormalization(center=True, scale=True),\n",
        "      tf.keras.layers.ReLU()\n",
        "  ])\n",
        "\n",
        "class ConvN(tf.keras.Model):\n",
        "\n",
        "  def __init__(self, width_multiplier):\n",
        "    super(ConvN, self).__init__()\n",
        "    self.num_classes = 10\n",
        "    self.latent_dim = 128 * width_multiplier\n",
        "    self.proj_dim = self.latent_dim / 2\n",
        "\n",
        "    self.enc = tf.keras.Sequential([\n",
        "        conv2d_bn_relu(32 * width_multiplier, 3, 2),\n",
        "        conv2d_bn_relu(64 * width_multiplier, 3, 2),\n",
        "        conv2d_bn_relu(64 * width_multiplier, 3, 2),\n",
        "        tf.keras.layers.Flatten(),\n",
        "        dense_bn_relu(self.latent_dim)\n",
        "    ])\n",
        "\n",
        "    self.proj = tf.keras.Sequential([\n",
        "        dense_bn_relu(self.latent_dim * 2),\n",
        "        tf.keras.layers.Dense(\n",
        "            self.proj_dim, use_bias=False, activation=None,\n",
        "            kernel_regularizer=tf.keras.regularizers.l2(1e-4)),\n",
        "    ])\n",
        "\n",
        "    self.classifier = tf.keras.layers.Dense(self.num_classes)\n",
        "\n",
        "  def call(self, inputs, training):\n",
        "    y = self.enc(inputs, training)\n",
        "    z = self.proj(y, training)\n",
        "    pred = self.classifier(tf.stop_gradient(y))\n",
        "    return y, z, pred"
      ],
      "execution_count": 5,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "u9F2LOzm2zy4",
        "cellView": "form"
      },
      "source": [
        "#@title Define train_and_eval() for contrastive learning.\n",
        "def train_and_eval(\n",
        "    batch_size=128,\n",
        "    width_multiplier=1,\n",
        "    extra_channel_bits=10,\n",
        "    nt_xent_temp=0.1,\n",
        "    learning_rate=0.001,\n",
        "    epochs=10,\n",
        "    log_summary_every_n_steps=100,\n",
        "    eval_every_n_steps=100,\n",
        "    print_output=False):\n",
        "  strategy = tf.distribute.MirroredStrategy()\n",
        "\n",
        "  # Load dataset.\n",
        "  builder = tfds.builder('mnist')\n",
        "  builder.download_and_prepare()\n",
        "\n",
        "  preprocess_train_fn, preprocess_eval_fn = get_process_fns(extra_channel_bits)\n",
        "  train_dataset = builder.as_dataset(split='train', as_supervised=True)\n",
        "  train_dataset = train_dataset.repeat().map(preprocess_train_fn)\n",
        "  train_dataset = train_dataset.batch(batch_size)\n",
        "\n",
        "  test_dataset = builder.as_dataset(split='test', as_supervised=True)\n",
        "  test_dataset = test_dataset.map(preprocess_eval_fn)\n",
        "  test_dataset = test_dataset.batch(batch_size, drop_remainder=False)\n",
        "\n",
        "  train_iter = iter(train_dataset)\n",
        "  total_steps = int(60000 * epochs / batch_size)\n",
        "  steps_per_epoch_test = math.ceil(10000 / batch_size)\n",
        "\n",
        "  # Model and optimizer.\n",
        "  model = ConvN(width_multiplier)\n",
        "  global_step = tf.Variable(\n",
        "      1, trainable=False, name=\"global_step\", dtype=tf.int64)\n",
        "\n",
        "  lr_schedule = tf.keras.optimizers.schedules.PolynomialDecay(\n",
        "      initial_learning_rate=learning_rate,\n",
        "      decay_steps=total_steps,\n",
        "      end_learning_rate=0.)\n",
        "  optimizer = tf.keras.optimizers.Adam(lr_schedule)\n",
        "\n",
        "  # Define metrics.\n",
        "  loss_metrics = [\n",
        "      \"train_classification_loss\",\n",
        "      \"train_contrastive_loss\",\n",
        "      \"train_total_loss\",\n",
        "  ]\n",
        "  acc_metrics = [\"train_accuracy\", \"eval_accuracy\",\n",
        "                 \"train_contrastive_accuracy\"]\n",
        "  metric_list = {s: tf.keras.metrics.Mean(name=s) for s in loss_metrics}\n",
        "  metric_list.update({\n",
        "      s: tf.keras.metrics.SparseCategoricalAccuracy(name=s)\n",
        "      for s in acc_metrics\n",
        "  })\n",
        "\n",
        "  # Step functions.\n",
        "  @tf.function\n",
        "  def train_step(iterator):\n",
        "    def step_fn(inputs):\n",
        "      images, labels = inputs\n",
        "      labels_one_hot = tf.one_hot(labels, depth=10)\n",
        "      images_a, images_b = tf.unstack(images, num=2, axis=1)\n",
        "\n",
        "      with tf.GradientTape() as tape:\n",
        "        _, za, pred_a = model(images_a, training=True)\n",
        "        _, zb, pred_b = model(images_b, training=True)\n",
        "\n",
        "        contrastive_loss, contrastive_sim = (\n",
        "            get_contrastive_loss(za, zb, nt_xent_temp))\n",
        "        classifier_loss = get_cls_loss(labels_one_hot, pred_a)\n",
        "        wd_loss = sum(model.losses)\n",
        "        loss = classifier_loss + wd_loss + contrastive_loss\n",
        "\n",
        "        batch_size = tf.shape(images)[0]\n",
        "        metric_list[\"train_contrastive_loss\"].update_state(contrastive_loss)\n",
        "        metric_list[\"train_classification_loss\"].update_state(classifier_loss)\n",
        "        metric_list[\"train_accuracy\"].update_state(labels, pred_a)\n",
        "        metric_list[\"train_contrastive_accuracy\"].update_state(\n",
        "            tf.range(batch_size), contrastive_sim)\n",
        "        metric_list[\"train_total_loss\"].update_state(loss)\n",
        "\n",
        "      gradients = tape.gradient(loss, model.trainable_variables)\n",
        "      optimizer.apply_gradients(zip(gradients, model.trainable_variables))\n",
        "\n",
        "    strategy.run(step_fn, args=(next(iterator),))\n",
        "    global_step.assign_add(1)\n",
        "\n",
        "  @tf.function\n",
        "  def eval_step(iterator):\n",
        "    def step_fn(inputs):\n",
        "      images, labels = inputs\n",
        "      _, _, predictions = model(images, training=False)\n",
        "      metric_list[\"eval_accuracy\"].update_state(labels, predictions)\n",
        "    strategy.run(step_fn, args=(next(iterator),))\n",
        "\n",
        "  # Train and eval loop.\n",
        "  steps = []\n",
        "  eval_accuracies = []\n",
        "  while global_step.numpy() <= total_steps:\n",
        "    train_step(train_iter)\n",
        "    step = global_step.numpy()\n",
        "\n",
        "    if step % log_summary_every_n_steps == 0:\n",
        "      log_msg = \"Steps: {}\".format(step)\n",
        "      for m in loss_metrics + acc_metrics:\n",
        "        if m.startswith(\"train\"):\n",
        "          log_msg += \", {}: {}\".format(m, metric_list[m].result())\n",
        "          metric_list[m].reset_states()\n",
        "      if print_output:\n",
        "        print(log_msg)\n",
        "\n",
        "    if (step % eval_every_n_steps == 0) or (step == total_steps):\n",
        "      eval_iter = iter(test_dataset)\n",
        "\n",
        "      for m in loss_metrics + acc_metrics:\n",
        "        if m.startswith(\"eval\"):\n",
        "          metric_list[m].reset_states()\n",
        "\n",
        "      for _ in range(steps_per_epoch_test):\n",
        "        eval_step(eval_iter)\n",
        "      if print_output:\n",
        "        print(\"Steps: {}, Test accuracy: {}\".format(\n",
        "            step, metric_list[\"eval_accuracy\"].result()))\n",
        "      steps.append(step)\n",
        "      eval_accuracies.append(metric_list[\"eval_accuracy\"].result())\n",
        "\n",
        "  return steps, eval_accuracies"
      ],
      "execution_count": 6,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "I3an389J6gCq",
        "outputId": "e4f63f0d-64ba-4318-e197-e01e26575aee",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000,
          "referenced_widgets": [
            "d8806065a5054b8da9767e4bb24af477",
            "1747f5b05a9c480c8311e56b9be11d2a",
            "8ecae8435e1244e2bac2b3a75971cdef",
            "bce306cd6a544f2a8b7fdbcfe523d802",
            "b8c000a9d5974ea58a96d724eaf43beb",
            "8f57b328127541f0b1f48bd4ad1a016c",
            "741b86714bdc4b1c8ee4eafcbf8d4115",
            "d0d83413d905432588674bed26b36979"
          ]
        }
      },
      "source": [
        "# The actual training. This cell takes a long time to run, especially on a CPU.\n",
        "rows = []\n",
        "cols = ['extra_channel_bits', 'nt_xent_temp', 'eval_accuracy']\n",
        "for temp in [0.05, 0.1, 0.2]:\n",
        "  for bits in [0, 5, 10, 15, 20]:\n",
        "    steps, accuracies = train_and_eval(\n",
        "        batch_size=1000,\n",
        "        width_multiplier=1,\n",
        "        extra_channel_bits=bits,\n",
        "        nt_xent_temp=temp,\n",
        "        learning_rate=0.001,\n",
        "        epochs=10,\n",
        "        eval_every_n_steps=1000,\n",
        "        print_output=False)\n",
        "    rows.append([bits, temp, accuracies[-1].numpy()])\n",
        "    print(\"extra_channel_bits={}, nt_xent_temp={}, eval_accuracy={}\".format(\n",
        "        bits, temp, accuracies[-1]))\n",
        "plot_df = pd.DataFrame.from_records(rows, columns=cols)"
      ],
      "execution_count": 7,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n",
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n",
            "\u001b[1mDownloading and preparing dataset mnist/3.0.1 (download: 11.06 MiB, generated: 21.00 MiB, total: 32.06 MiB) to /root/tensorflow_datasets/mnist/3.0.1...\u001b[0m\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:absl:Dataset mnist is hosted on GCS. It will automatically be downloaded to your\n",
            "local data directory. If you'd instead prefer to read directly from our public\n",
            "GCS bucket (recommended if you're running on GCP), you can instead pass\n",
            "`try_gcs=True` to `tfds.load` or set `data_dir=gs://tfds-data/datasets`.\n",
            "\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "display_data",
          "data": {
            "application/vnd.jupyter.widget-view+json": {
              "model_id": "d8806065a5054b8da9767e4bb24af477",
              "version_minor": 0,
              "version_major": 2
            },
            "text/plain": [
              "HBox(children=(FloatProgress(value=0.0, description='Dl Completed...', max=4.0, style=ProgressStyle(descriptio…"
            ]
          },
          "metadata": {
            "tags": []
          }
        },
        {
          "output_type": "stream",
          "text": [
            "\n",
            "\n",
            "\u001b[1mDataset mnist downloaded and prepared to /root/tensorflow_datasets/mnist/3.0.1. Subsequent calls will reuse this data.\u001b[0m\n",
            "WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/util/dispatch.py:201: sample_distorted_bounding_box (from tensorflow.python.ops.image_ops_impl) is deprecated and will be removed in a future version.\n",
            "Instructions for updating:\n",
            "`seed2` arg is deprecated.Use sample_distorted_bounding_box_v2 instead.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/util/dispatch.py:201: sample_distorted_bounding_box (from tensorflow.python.ops.image_ops_impl) is deprecated and will be removed in a future version.\n",
            "Instructions for updating:\n",
            "`seed2` arg is deprecated.Use sample_distorted_bounding_box_v2 instead.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=0, nt_xent_temp=0.05, eval_accuracy=0.8992000222206116\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=5, nt_xent_temp=0.05, eval_accuracy=0.7063000202178955\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=10, nt_xent_temp=0.05, eval_accuracy=0.32989999651908875\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=15, nt_xent_temp=0.05, eval_accuracy=0.14259999990463257\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=20, nt_xent_temp=0.05, eval_accuracy=0.0989999994635582\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=0, nt_xent_temp=0.1, eval_accuracy=0.8916000127792358\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=5, nt_xent_temp=0.1, eval_accuracy=0.6118000149726868\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=10, nt_xent_temp=0.1, eval_accuracy=0.1867000013589859\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=15, nt_xent_temp=0.1, eval_accuracy=0.09799999743700027\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=20, nt_xent_temp=0.1, eval_accuracy=0.10329999774694443\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=0, nt_xent_temp=0.2, eval_accuracy=0.8998000025749207\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=5, nt_xent_temp=0.2, eval_accuracy=0.32989999651908875\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=10, nt_xent_temp=0.2, eval_accuracy=0.10350000113248825\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=15, nt_xent_temp=0.2, eval_accuracy=0.10029999911785126\n",
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0',)\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "extra_channel_bits=20, nt_xent_temp=0.2, eval_accuracy=0.09889999777078629\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "svvRfGG76zxz",
        "outputId": "da318fd2-71ff-4254-eb90-58822d4b3b3a",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 352
        }
      },
      "source": [
        "plt.figure(figsize=(5, 5))\n",
        "sns.lineplot(\n",
        "    x='extra_channel_bits', y='eval_accuracy', hue='nt_xent_temp',\n",
        "    data=plot_df, palette=sns.color_palette(\"mako_r\", 3))"
      ],
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<matplotlib.axes._subplots.AxesSubplot at 0x7faa3a73cdd8>"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 8
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 360x360 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    }
  ]
}
